#pragma once

#include "LightChangeBehavior.h"
#include "World/World.h"
#include "Scene/Level.h"

#include <queue>

void LightChangeBehavior::OnChunkLoaded(const Ref<Chunk> &chunk, World &world)
{

  RefreshVerticeLightData(chunk, world);
}

void LightChangeBehavior::OnBlockPlace(Player *player, glm::ivec3 blockWorldPos, const BlockData *block, World &world)
{
  Ref<Chunk> chunk = world.GetChunk(World::GetChunkIndex(blockWorldPos));
  if (!chunk)
    return;

  // 区块内光源列表
  auto &lightSources = chunk->lightSources;

  // 如果是光源方块，添加
  const auto id = block->id;
  Block *blockPtr = world.GetLevel().GetBlockRegistry()->GetBlock(id);

  if (blockPtr->lightLevel > 0)
  {
    lightSources.push_back({blockWorldPos, blockPtr->lightLevel});
  }

  if (!player)
    return;

  RefreshVerticeLightData(chunk, world);
}

void LightChangeBehavior::OnBlockBreak(Player *player, glm::ivec3 blockWorldPos, const BlockData *block, World &world)
{
  Ref<Chunk> chunk = world.GetChunk(World::GetChunkIndex(blockWorldPos));
  if (!chunk)
    return;

  // 区块内光源列表
  auto &lightSources = chunk->lightSources;

  // 如果是光源方块，移除
  const Block *blockPtr = world.GetLevel().GetBlockRegistry()->GetBlock(block->id);
  if (blockPtr->lightLevel > 0)
  {
    for (auto it = lightSources.begin(); it != lightSources.end(); ++it)
    {
      if (it->position == blockWorldPos)
      {
        lightSources.erase(it);
        break;
      }
    }
  }
}

void LightChangeBehavior::BFSLighting(const std::vector<LightSource> &worldPosition, World &world)
{
  if (worldPosition.empty())
    return;

  Ref<Chunk> chunk = world.GetChunk(World::GetChunkIndex(worldPosition[0].position));
  if (!chunk)
    return;

  const glm::ivec2 &chunkIndex = chunk->GetPosition();

  std::queue<LightSource> lightQueue;
  m_bfsVisitedSet.clear();

  // 初始化
  for (const auto &pos : worldPosition)
  {
    if (m_bfsVisitedSet.find(pos.position) != m_bfsVisitedSet.end())
      continue;
    m_bfsVisitedSet.insert(pos.position);
    lightQueue.push(pos);
  }

  // 三维BFS
  while (!lightQueue.empty())
  {
    auto [currentPos, currentLevel] = lightQueue.front();
    lightQueue.pop();

    // 如果光照等级为0，跳过
    if (currentLevel <= 0)
      continue;

    // 更新顶点光照数据
    glm::ivec3 inChunkPos{0};

    // 获取邻居位置的区块
    inChunkPos = World::ToPositionInChunk(currentPos);
    Ref<Chunk> currentChunk = world.GetChunk(World::GetChunkIndex(currentPos));
    if (!currentChunk)
      continue;

    currentChunk->SetDirty();
    uint8_t &originalLightLevel = currentChunk->vertexLightData[inChunkPos.x][inChunkPos.y][inChunkPos.z];
    if (originalLightLevel < currentLevel)
      originalLightLevel = currentLevel;

    // 遍历光源的六个方向
    constexpr static const std::array<glm::ivec3, 6> dirVec{{
        {1, 0, 0},  // x+
        {-1, 0, 0}, // x-
        {0, 1, 0},  // y+
        {0, -1, 0}, // y-
        {0, 0, 1},  // z+
        {0, 0, -1}, // z-
    }};
    for (int i = 0; i < 6; ++i)
    {
      const glm::ivec3 neighborPos = currentPos + dirVec[i];
      if (neighborPos.y < 0 || neighborPos.y >= Chunk::VerticalSize)
        continue;

      // 检查是否已经访问过
      if (m_bfsVisitedSet.find(neighborPos) != m_bfsVisitedSet.end())
        continue;
      // 标记当前位置为已访问
      m_bfsVisitedSet.insert(neighborPos);

      // 获取邻居的方块
      BlockData *neighborBlock = neighborBlock = world.GetBlockAtIfLoaded(neighborPos);

      if (neighborBlock)
      {
        Block *neighborBlockPtr = world.GetLevel().GetBlockRegistry()->GetBlock(neighborBlock->id);
        if (neighborBlockPtr->renderClass == Block::RenderType::Opaque)
          continue;
      }
      if (currentLevel > 1)
        lightQueue.push({neighborPos, currentLevel - 1u});
    }
  }
}

void LightChangeBehavior::RefreshVerticeLightData(const Ref<Chunk> &chunk, World &world)
{
  const glm::ivec2 &chunkIndex = chunk->GetPosition();

  std::vector<LightSource> lightSources;

  for (int x = -1; x <= 1; ++x)
  {
    for (int y = -1; y <= 1; ++y)
    {
      const glm::ivec2 neighborPos = chunk->GetPosition() + glm::ivec2(x, y) * Chunk::HorizontalSize;
      Ref<Chunk> neighborChunk = world.GetChunk(neighborPos);

      if (neighborChunk)
      {
        const auto lights = neighborChunk->lightSources;
        lightSources.insert(lightSources.end(), lights.begin(), lights.end());

        memset(neighborChunk->vertexLightData, 0, sizeof(neighborChunk->vertexLightData));
      }
    }
  }

  BFSLighting(lightSources, world);
}
